package demo.designPattern.aop;

import demo.java.lang.instrument.MyClassFileTransformer;
import demo.javassist.TranslatorDemo;
import demo.spring.service.IActions;
import demo.spring.service.impl.DemoService;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.Loader;
import javassist.NotFoundException;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * <h2>Spring的AOP</h2> Spring默认采取动态代理机制实现AOP，当动态代理不可用时（代理类无接口）会使用cglib机制。但Spring的AOP有一定的缺点：
 * <li>第一，只能对方法进行切入，不能对接口、字段、静态代码块进行切入（切入接口的某个方法，则该接口下所有实现类的该方法都将被切入）
 * <li>第二，同类中的互相调用方法将不会使用代理类。因为要使用代理类必须从Spring容器中获取Bean
 * <li>第三，性能不是最好的。从前面几节得知，我们自定义的类加载器，性能优于动态代理和cglib
 *
 */
public class AopDemo {

    public static void main(String[] args) {
        testSpringDynamicProxy();
    }

    /**
     * 获取被代理对象
     * 
     * @param ref
     * @return
     */
    protected Class getServiceClass(Object ref) {
        if (AopUtils.isAopProxy(ref)) {
            return AopUtils.getTargetClass(ref);
        }
        return ref.getClass();
    }

    static void test() {
        ApplicationContext cxt = new ClassPathXmlApplicationContext("spring/beans.xml");
        IActions personService = (IActions) cxt.getBean("personService");
        personService.say("hello aop.");
    }

    /**
     * 目标对象有实现接口，spring会自动选择"jdk代理【动态代理】",动态代理的标识：class com.sun.proxy.$Proxy10
     */
    static void testSpringDynamicProxy() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring/testAopBean.xml");
        IActions dao = (IActions) ac.getBean("actions");
        System.err.println(dao.getClass());
        dao.say("hello aop.");
    }

    /**
     * 目标对象没有实现接口，spring会用"cglib代理哦"，标识：class **$$EnhancerByCGLIB$$4952a60a
     */
    static void testSpringCglib() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring/testAopBean.xml");
        DemoService dao = (DemoService) ac.getBean("demoService");
        System.err.println(dao.getClass());
        dao.say("hello aop.");
    }

    /**
     * <h2>使用Instrumentation和javassist来实现AOP</h2>
     * 
     * 自定义类加载器实现AOP只能拦截自己加载的字节码，那么有一种方式能够监控所有类加载器加载的字节码吗？
     * 有，使用Instrumentation，它是Java5的新特性，使用Instrument，开发者可以构建一个字节码转换器，在字节码加载前进行转换。
     * <h3>配置和执行</h3>
     * 
     * 需要告诉JVM在启动main函数之前，需要先执行premain函数。
     * 
     * <li>首先，需要将premain函数所在的类打成jar包，并修改jar包里的META-INF\MANIFEST.MF文件
     * 
     * <pre>
    Manifest-Version: 1.0 
    Premain-Class: bci. MyClassFileTransformer
     * </pre>
     * 
     * <li>其次，在JVM的启动参数里加上-javaagent:D:\java\projects\opencometProject\Aop\lib\aop.jar
     */
    static void testInstrumentation() {
        MyClassFileTransformer.premain("", null);
    }

    /**
     * <h2>自定义类加载器</h2>
     * 
     * 如果我们实现了一个自定义类加载器，在类加载到JVM之前直接修改某些类的方法，并将切入逻辑织入到这个方法里，然后将修改后的字节码文件交给虚拟机运行，那岂不是更直接。
     * Javassist是一个编辑字节码的框架，可以让你很简单地操作字节码。它可以在运行期定义或修改Class。使用Javassist实现AOP的原理是在字节码加载前直接修改需要切入的方法。
     * 这比使用cglib实现AOP更加高效，并且没有太多限制：我们使用类加载器启动我们自定义的类加载器，在这个类加载器里加一个类加载监听器，监听器发现目标类被加载时就织入切入逻辑。
     * 使用自定义的类加载器实现AOP在性能上有优于动态代理和cglib，因为它不会产生新类，但是它仍人存在一个问题，就是如果其他的类加载器来加载类的话，这些类就不会被拦截。
     */
    static void testJavassistAop() throws Throwable {
        // 获取存放CtClass的容器ClassPool
        ClassPool classPool = ClassPool.getDefault();
        // 创建一个类加载器
        Loader loader = new Loader();
        // 增加一个转换器
        try {
            loader.addTranslator(classPool, new TranslatorDemo());
        } catch (NotFoundException | CannotCompileException e) {
            e.printStackTrace();
        }
        // 启动MyTranslator的main函数
        try {
            loader.run("demo.javassist.TranslatorDemo", null);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

}
